using System;

namespace Com.FirstSolver.Splash;

public class SM2
{
	public readonly BigInteger mP;

	public readonly BigInteger mA;

	public readonly BigInteger mB;

	public readonly BigInteger mN;

	public readonly BigInteger mGx;

	public readonly BigInteger mGy;

	public readonly FpCurve mCurve;

	public readonly FpPoint mPointG;

	public readonly int mFieldSizeInBytes;

	public static readonly string[] EC256 = new string[6] { "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF", "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC", "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93", "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123", "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7", "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0" };

	public static readonly string[] NISTEC192 = new string[6] { "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFC", "64210519E59C80E70FA7E9AB72243049FEB8DEECC146B9B1", "FFFFFFFFFFFFFFFFFFFFFFFF99DEF836146BC9B1B4D22831", "188DA80EB03090F67CBF20EB43A18800F4FF0AFD82FF1012", "07192B95FFC8DA78631011ED6B24CDD573F977A11E794811" };

	public static readonly string[] NISTEC224 = new string[6] { "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000001", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFE", "B4050A850C04B3ABF54132565044B0B7D7BFD8BA270B39432355FFB4", "FFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D", "B70E0CBD6BB4BF7F321390B94A03C1D356C21122343280D6115C1D21", "BD376388B5F723FB4C22DFE6CD4375A05A07476444D5819985007E34" };

	public static readonly string[] NISTEC256 = new string[6] { "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", "5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B", "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551", "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296", "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5" };

	public static readonly string[] NISTEC384 = new string[6] { "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC", "B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF", "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973", "AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7", "3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F" };

	public static readonly string[] NISTEC521 = new string[6] { "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC", "0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00", "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409", "00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66", "011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650" };

	private static readonly byte[] BitsMaskInByte = new byte[8] { 255, 127, 63, 31, 15, 7, 3, 1 };

	public string KeyExchangeAlgorithm => "SM2DiffieHellman";

	public string SignatureAlgorithm => "SM2Dsa";

	public string HashAlgorithm => "SM3";

	public SM2(BigInteger p, BigInteger a, BigInteger b, BigInteger n, BigInteger gx, BigInteger gy)
	{
		mP = p;
		mA = a;
		mB = b;
		mN = n;
		mGx = gx;
		mGy = gy;
		mCurve = new FpCurve(mP, mA, mB);
		mPointG = new FpPoint(mCurve, new FpFieldElement(mP, mGx), new FpFieldElement(mP, mGy));
		mFieldSizeInBytes = p.BitLength + 7 >> 3;
	}

	public SM2(string p, string a, string b, string n, string gx, string gy)
		: this(new BigInteger(p, 16), new BigInteger(a, 16), new BigInteger(b, 16), new BigInteger(n, 16), new BigInteger(gx, 16), new BigInteger(gy, 16))
	{
	}

	public SM2()
		: this(EC256[0], EC256[1], EC256[2], EC256[3], EC256[4], EC256[5])
	{
	}

	public static SM2 CreateInstance(int bitLength)
	{
		return bitLength switch
		{
			192 => new SM2(NISTEC192[0], NISTEC192[1], NISTEC192[2], NISTEC192[3], NISTEC192[4], NISTEC192[5]), 
			224 => new SM2(NISTEC224[0], NISTEC224[1], NISTEC224[2], NISTEC224[3], NISTEC224[4], NISTEC224[5]), 
			256 => new SM2(NISTEC256[0], NISTEC256[1], NISTEC256[2], NISTEC256[3], NISTEC256[4], NISTEC256[5]), 
			384 => new SM2(NISTEC384[0], NISTEC384[1], NISTEC384[2], NISTEC384[3], NISTEC384[4], NISTEC384[5]), 
			521 => new SM2(NISTEC521[0], NISTEC521[1], NISTEC521[2], NISTEC521[3], NISTEC521[4], NISTEC521[5]), 
			_ => throw new NotImplementedException(), 
		};
	}

	public virtual byte[] EncryptValue(byte[] data, ECPoint publicKey)
	{
		if (publicKey.IsInfinity)
		{
			return null;
		}
		while (true)
		{
			ECPoint C1;
			ECPoint P2 = publicKey.Multiply(GetKeyPair(out C1));
			using SM3 sm3C2 = new SM3();
			sm3C2.BlockUpdate(GetEncoded(P2.mX));
			using SM3 sm3C3 = new SM3(sm3C2);
			byte[] EncodedP2Y = GetEncoded(P2.mY);
			sm3C2.BlockUpdate(EncodedP2Y);
			byte[] T = SM3.KDF(sm3C2, data.Length);
			if (Array.TrueForAll(T, (byte b) => b == 0))
			{
				continue;
			}
			sm3C3.BlockUpdate(data);
			sm3C3.BlockUpdate(EncodedP2Y);
			byte[] C2 = sm3C3.DoFinal();
			byte[] EncodedC1 = GetEncoded(C1);
			int Index = EncodedC1.Length - 1;
			byte[] R = new byte[Index + data.Length + 32];
			Array.Copy(EncodedC1, 1, R, 0, Index);
			for (int i = 0; i < data.Length; i++)
			{
				R[Index++] = (byte)(data[i] ^ T[i]);
			}
			Array.Copy(C2, 0, R, Index, 32);
			return R;
		}
	}

	public virtual byte[] DecryptValue(byte[] data, BigInteger privateKey)
	{
		int MOff = mFieldSizeInBytes << 1;
		int MLen = data.Length - MOff - 32;
		if (MLen <= 0)
		{
			return null;
		}
		BigInteger X = new BigInteger(1, data, 0, mFieldSizeInBytes);
		BigInteger Y = new BigInteger(1, data, mFieldSizeInBytes, mFieldSizeInBytes);
		ECPoint C1 = new FpPoint(mCurve, mCurve.FromBigInteger(X), mCurve.FromBigInteger(Y), withCompression: false);
		if (!Exist(C1))
		{
			return null;
		}
		ECPoint P2 = C1.Multiply(privateKey);
		using SM3 sm3C2 = new SM3();
		sm3C2.BlockUpdate(GetEncoded(P2.mX));
		using SM3 sm3C3 = new SM3(sm3C2);
		byte[] EncodedP2Y = GetEncoded(P2.mY);
		sm3C2.BlockUpdate(EncodedP2Y);
		byte[] T = SM3.KDF(sm3C2, MLen);
		if (Array.TrueForAll(T, (byte b) => b == 0))
		{
			return null;
		}
		byte[] M = new byte[MLen];
		for (int j = 0; j < MLen; j++)
		{
			M[j] = (byte)(data[MOff++] ^ T[j]);
		}
		sm3C3.BlockUpdate(M);
		sm3C3.BlockUpdate(EncodedP2Y);
		byte[] C2 = sm3C3.DoFinal();
		for (int i = 0; i < 32; i++)
		{
			if (C2[i] != data[MOff++])
			{
				return null;
			}
		}
		return M;
	}

	public virtual byte[] SignData(byte[] data, int offset, int count, byte[] userId, BigInteger privateKey)
	{
		return SignDataWithZ(data, offset, count, ComputeZ(userId, mPointG.Multiply(privateKey)), privateKey);
	}

	public virtual byte[] SignData(byte[] data, byte[] userId, BigInteger privateKey)
	{
		return SignDataWithZ(data, 0, data.Length, ComputeZ(userId, mPointG.Multiply(privateKey)), privateKey);
	}

	public virtual byte[] SignDataWithZ(byte[] data, int offset, int count, byte[] Z, BigInteger privateKey)
	{
		if (Z.Length != 32)
		{
			return null;
		}
		BigInteger e;
		using (SM3 sm3 = new SM3())
		{
			sm3.BlockUpdate(Z);
			sm3.BlockUpdate(data, offset, count);
			e = new BigInteger(1, sm3.DoFinal());
		}
		BigInteger r;
		BigInteger s;
		while (true)
		{
			ECPoint P1;
			BigInteger i = GetKeyPair(out P1);
			r = e.Add(P1.mX.ToBigInteger()).Mod(mN);
			if (!r.Equals(BigInteger.Zero) && !r.Add(i).Equals(mN))
			{
				s = privateKey.Add(BigInteger.One).ModInverse(mN).Multiply(i.Subtract(r.Multiply(privateKey)).Mod(mN))
					.Mod(mN);
				if (!s.Equals(BigInteger.Zero))
				{
					break;
				}
			}
		}
		byte[] R = new byte[mFieldSizeInBytes << 1];
		GetEncoded(r).CopyTo(R, 0);
		GetEncoded(s).CopyTo(R, mFieldSizeInBytes);
		return R;
	}

	public virtual byte[] SignDataWithZ(byte[] data, byte[] Z, BigInteger privateKey)
	{
		return SignDataWithZ(data, 0, data.Length, Z, privateKey);
	}

	public virtual bool VerifyData(byte[] data, int offset, int count, byte[] userId, ECPoint publicKey, byte[] signature)
	{
		return VerifyDataWithZ(data, offset, count, ComputeZ(userId, publicKey), publicKey, signature);
	}

	public virtual bool VerifyData(byte[] data, byte[] userId, ECPoint publicKey, byte[] signature)
	{
		return VerifyDataWithZ(data, 0, data.Length, ComputeZ(userId, publicKey), publicKey, signature);
	}

	public virtual bool VerifyDataWithZ(byte[] data, int offset, int count, byte[] Z, ECPoint publicKey, byte[] signature)
	{
		if (signature.Length != mFieldSizeInBytes << 1)
		{
			return false;
		}
		if (Z.Length != 32)
		{
			return false;
		}
		BigInteger r = new BigInteger(1, signature, 0, mFieldSizeInBytes);
		if (r.CompareTo(BigInteger.Zero) != 1 || r.CompareTo(mN) != -1)
		{
			return false;
		}
		BigInteger s = new BigInteger(1, signature, mFieldSizeInBytes, mFieldSizeInBytes);
		if (s.CompareTo(BigInteger.Zero) != 1 || s.CompareTo(mN) != -1)
		{
			return false;
		}
		BigInteger e;
		using (SM3 sm3 = new SM3())
		{
			sm3.BlockUpdate(Z);
			sm3.BlockUpdate(data, offset, count);
			e = new BigInteger(1, sm3.DoFinal());
		}
		BigInteger t = r.Add(s).Mod(mN);
		if (t.Equals(BigInteger.Zero))
		{
			return false;
		}
		ECPoint P1 = mPointG.Multiply(s).Add(publicKey.Multiply(t));
		BigInteger R = e.Add(P1.mX.ToBigInteger()).Mod(mN);
		return R.Equals(r);
	}

	public virtual bool VerifyDataWithZ(byte[] data, byte[] Z, ECPoint publicKey, byte[] signature)
	{
		return VerifyDataWithZ(data, 0, data.Length, Z, publicKey, signature);
	}

	public virtual bool KeyAgreement(SM2KeyExchangeInformation Information, bool IsInitiator, int sharedKeyBytes, out byte[] SharedKey, bool withConfirm = true)
	{
		SharedKey = null;
		if (!Exist(Information.PartnerPublicKey) || !Exist(Information.PartnerR))
		{
			return false;
		}
		if (Information.PartnerZ == null || Information.PartnerZ.Length != 32)
		{
			return false;
		}
		BigInteger W1 = BigInteger.One.ShiftLeft((mN.BitLength >> 1) - 1);
		BigInteger W2 = W1.Subtract(BigInteger.One);
		BigInteger X = W1.Add(Information.R.mX.ToBigInteger().And(W2));
		BigInteger T = Information.PrivateKey.Add(Information.r.Multiply(X)).Mod(mN);
		BigInteger PartnerX = W1.Add(Information.PartnerR.mX.ToBigInteger().And(W2));
		ECPoint V = Information.PartnerPublicKey.Add(Information.PartnerR.Multiply(PartnerX)).Multiply(T);
		if (V.IsInfinity)
		{
			return false;
		}
		byte[] Z = new byte[(mFieldSizeInBytes << 1) + 64];
		GetEncoded(V.mX).CopyTo(Z, 0);
		GetEncoded(V.mY).CopyTo(Z, mFieldSizeInBytes);
		if (IsInitiator)
		{
			Information.Z.CopyTo(Z, mFieldSizeInBytes << 1);
			Information.PartnerZ.CopyTo(Z, (mFieldSizeInBytes << 1) + 32);
		}
		else
		{
			Information.PartnerZ.CopyTo(Z, mFieldSizeInBytes << 1);
			Information.Z.CopyTo(Z, (mFieldSizeInBytes << 1) + 32);
		}
		SharedKey = SM3.KDF(Z, sharedKeyBytes);
		if (withConfirm)
		{
			using SM3 sm3 = new SM3();
			sm3.BlockUpdate(GetEncoded(V.mX));
			if (IsInitiator)
			{
				sm3.BlockUpdate(Information.Z);
				sm3.BlockUpdate(Information.PartnerZ);
				sm3.BlockUpdate(GetEncoded(Information.R.mX));
				sm3.BlockUpdate(GetEncoded(Information.R.mY));
				sm3.BlockUpdate(GetEncoded(Information.PartnerR.mX));
				sm3.BlockUpdate(GetEncoded(Information.PartnerR.mY));
			}
			else
			{
				sm3.BlockUpdate(Information.PartnerZ);
				sm3.BlockUpdate(Information.Z);
				sm3.BlockUpdate(GetEncoded(Information.PartnerR.mX));
				sm3.BlockUpdate(GetEncoded(Information.PartnerR.mY));
				sm3.BlockUpdate(GetEncoded(Information.R.mX));
				sm3.BlockUpdate(GetEncoded(Information.R.mY));
			}
			byte[] HashVX = sm3.DoFinal();
			sm3.Update(2);
			sm3.BlockUpdate(GetEncoded(V.mY));
			sm3.BlockUpdate(HashVX);
			Information.S1 = sm3.DoFinal();
			sm3.Update(3);
			sm3.BlockUpdate(GetEncoded(V.mY));
			sm3.BlockUpdate(HashVX);
			Information.S2 = sm3.DoFinal();
		}
		return true;
	}

	public virtual bool KeyConfirm(SM2KeyExchangeInformation Information, bool IsInitiator)
	{
		if (Information.PartnerS == null || Information.PartnerS.Length != 32)
		{
			return false;
		}
		if (IsInitiator)
		{
			for (int j = 0; j < 32; j++)
			{
				if (Information.S1[j] != Information.PartnerS[j])
				{
					return false;
				}
			}
		}
		else
		{
			for (int i = 0; i < 32; i++)
			{
				if (Information.S2[i] != Information.PartnerS[i])
				{
					return false;
				}
			}
		}
		return true;
	}

	public ECLicenseKey LicenseKeyMaker(byte[] userId, BigInteger PrivateKey)
	{
		ECPoint R;
		BigInteger r = GetKeyPair(out R);
		using SM3 sm3 = new SM3();
		sm3.BlockUpdate(userId);
		sm3.BlockUpdate(GetEncoded(R.x));
		sm3.BlockUpdate(GetEncoded(R.y));
		BigInteger hash = new BigInteger(1, sm3.DoFinal()).Mod(mN);
		return new ECLicenseKey(r.Subtract(hash.Multiply(PrivateKey)).Mod(mN), hash);
	}

	public ECLicenseKey LicenseKeyMaker(byte[] userId, BigInteger PrivateKey, BigInteger r)
	{
		if (r.CompareTo(BigInteger.Zero) <= 0 || r.CompareTo(mN.Subtract(BigInteger.One)) >= 0 || PrivateKey.Equals(r))
		{
			return null;
		}
		ECPoint R = mPointG.Multiply(r);
		using SM3 sm3 = new SM3();
		sm3.BlockUpdate(userId);
		sm3.BlockUpdate(GetEncoded(R.x));
		sm3.BlockUpdate(GetEncoded(R.y));
		BigInteger hash = new BigInteger(1, sm3.DoFinal()).Mod(mN);
		return new ECLicenseKey(r.Subtract(hash.Multiply(PrivateKey)).Mod(mN), hash);
	}

	public bool LicenseKeyVerifier(byte[] userId, ECLicenseKey RegisterCode, ECPoint PublicKey)
	{
		ECPoint R = mPointG.Multiply(RegisterCode.mKey).Add(PublicKey.Multiply(RegisterCode.mHash));
		using SM3 sm3 = new SM3();
		sm3.BlockUpdate(userId);
		sm3.BlockUpdate(GetEncoded(R.x));
		sm3.BlockUpdate(GetEncoded(R.y));
		return RegisterCode.mHash.Equals(new BigInteger(1, sm3.DoFinal()).Mod(mN));
	}

	public virtual BigInteger GetKeyPair(out ECPoint PublicKey)
	{
		int KeyFieldSize = mN.BitLength;
		int KeyLength = KeyFieldSize + 7 >> 3;
		int MaskBits = (KeyLength << 3) - KeyFieldSize;
		BigInteger d;
		do
		{
			byte[] b = Utils.GenerateRandom(KeyLength);
			b[0] &= BitsMaskInByte[MaskBits];
			d = new BigInteger(1, b);
		}
		while (d.CompareTo(BigInteger.Zero) <= 0 || d.CompareTo(mN.Subtract(BigInteger.One)) >= 0);
		PublicKey = mPointG.Multiply(d);
		return d;
	}

	public bool Exist(ECPoint value)
	{
		if (value == null || value.IsInfinity)
		{
			return false;
		}
		ECFieldElement X = value.mX;
		ECFieldElement Y = value.mY;
		return Y.Square().Equals(X.Square().Add(mCurve.FromBigInteger(mA)).Multiply(X)
			.Add(mCurve.FromBigInteger(mB)));
	}

	public virtual byte[] GetEncoded(ECPoint value, ECPointCompressionFlag flag = ECPointCompressionFlag.None)
	{
		if (value.IsInfinity)
		{
			return new byte[1];
		}
		byte[] X = GetEncoded(value.mX);
		byte[] PO;
		if (flag.Equals(ECPointCompressionFlag.Compression))
		{
			PO = new byte[mFieldSizeInBytes + 1];
			PO[0] = (byte)(value.mY.ToBigInteger().TestBit(0) ? 3u : 2u);
		}
		else
		{
			byte[] Y = GetEncoded(value.mY);
			PO = new byte[(mFieldSizeInBytes << 1) + 1];
			if (flag.Equals(ECPointCompressionFlag.None))
			{
				PO[0] = 4;
			}
			else
			{
				PO[0] = (byte)(value.mY.ToBigInteger().TestBit(0) ? 7u : 6u);
			}
			Y.CopyTo(PO, mFieldSizeInBytes + 1);
		}
		X.CopyTo(PO, 1);
		return PO;
	}

	public virtual byte[] GetEncoded(ECFieldElement value)
	{
		return GetEncoded(value.ToBigInteger());
	}

	public virtual byte[] GetEncoded(BigInteger value)
	{
		byte[] bytes = value.ToByteArrayUnsigned();
		if (bytes.Length > mFieldSizeInBytes)
		{
			byte[] tmp = new byte[mFieldSizeInBytes];
			Array.Copy(bytes, bytes.Length - mFieldSizeInBytes, tmp, 0, mFieldSizeInBytes);
			return tmp;
		}
		if (bytes.Length < mFieldSizeInBytes)
		{
			byte[] tmp2 = new byte[mFieldSizeInBytes];
			Array.Copy(bytes, 0, tmp2, mFieldSizeInBytes - bytes.Length, bytes.Length);
			return tmp2;
		}
		return bytes;
	}

	public byte[] ComputeZ(byte[] userId, ECPoint publicKey)
	{
		using SM3 sm3 = new SM3();
		int BitsLength = userId.Length << 3;
		sm3.Update((byte)((uint)(BitsLength >> 8) & 0xFFu));
		sm3.Update((byte)((uint)BitsLength & 0xFFu));
		sm3.BlockUpdate(userId);
		sm3.BlockUpdate(GetEncoded(mA));
		sm3.BlockUpdate(GetEncoded(mB));
		sm3.BlockUpdate(GetEncoded(mGx));
		sm3.BlockUpdate(GetEncoded(mGy));
		sm3.BlockUpdate(GetEncoded(publicKey.mX));
		sm3.BlockUpdate(GetEncoded(publicKey.mY));
		return sm3.DoFinal();
	}
}
